package clmasterdefaults;

#########################################################################
# Perl Object Module to load in the details of the master configuration	#
# XML defaults file and return an object with the details, or an 	#
# error if any are incorrect.						#
#########################################################################

use XML::Parser;
# use English;
use Data::Dumper;
use Carp qw(cluck croak confess);
use strict;

my $error_str=undef;
my $logmsg_hdlr=undef;
my $warnmsg_hdlr=undef;

sub error {
	return $error_str;
}

sub set_logmsg_hdlr {
	$logmsg_hdlr=$_[0];
}

sub set_warnmsg_hdlr {
	$warnmsg_hdlr=$_[0];
}

sub msg {
	if($_[0] eq "log" && defined($logmsg_hdlr)) {
		&{$logmsg_hdlr}($_[1]);
	}
	if($_[0] eq "warn" && defined($warnmsg_hdlr)) {
		&{$logmsg_hdlr}($_[1]);
	}
}

#########################################################################
# Class methods:							#
# ==============							#
# error - return latest error string or undef if everything was OK.	#
# set_logmsg_hdlr -  the routine to call to log a message - no logging	#
# 		     occurs if not set.					#
# set_warnmsg_hdlr - Issue a warning message via this routine - if	#
#		     available.						#
#########################################################################

#########################################################################
# 									#
# When called the mode can be specified. The mode is in the following	#
# format:								#
#									#
# filename	The name of the cluster configuration file.		#
# mode		The mode to run the configuration validation in		#
# flags		Further flags that alter tool.				#
#									#
# where:	mode = strict - do all checks, error on valid XML that	#
#				is not recognised.			#
#		     = normal - only error on valid XML			#
#									#
#		flags .. nochecksums - do not worry about looking for a	#
#					build checksum.			#
#									#
#########################################################################

sub new {
my $self={};
local $clmasterdefaults::__chardata="";
local %clmasterdefaults::defaults=();
my $class=shift;
my $mode=$_[0];
my $flags=$_[1];
my $file=$_[2];

	$file="$::ENV{TRUECL_CFG}/master-defaults.xml" if !defined($file);
	$mode="strict" if !defined($mode);
	$flags="" if !defined($flags);
	$error_str=undef;
	if(! -e $file) {
		$error_str="File $file does not exist.";
		return undef;
	}
	if(! -f $file) {
		$error_str="Object $file is not a regular file.";
		return undef;
	}
	if($mode eq "strict") {
		my @s=stat(_);
		if($s[2] & 07777 != 0600) {
			$error_str="Cluster configuration file $file must have 0600 permissions!";
			return undef;
		}
	}

	#################################################################
	# Now load in the XML using the XML::Parser - not XML simple.	#
	#################################################################
	
	my $p;
	$p = new XML::Parser(
			Handlers => { Start => \&lc_handle_start,
					End => \&lc_handle_end,
				       Char => \&lc_handle_char },
			ErrorContext => 5
			);
	$p->parsefile($file);
	if(defined($error_str)) {
		return undef;
	}
	$self->{FILE}=$file;

	$self->{DEFAULTS}=\%clmasterdefaults::defaults;
	bless($self);
	return $self;
}

sub lc_handle_start {
my $parser=shift;
my $element=shift;
my %attrs=(@_);
my @context=$parser->context;
my $data;

	$element=uc($element);
	$clmasterdefaults::__chardata="";

	return if $element eq "DEFAULTS";
	if($element eq "GLOBAL") {
		foreach (sort(keys(%attrs))) {
			my $ok=0;
			$data=$attrs{$_};
			if($_ eq "data") {
				if("$data" ne "replicated" && $data ne "shared") {
					$error_str.="\nData default of '$data' is not valid.";
					next;
				}
				$ok=1;
			}
			if($_ eq "do_statdb_locking") { next; }
			if($_ eq "logdir") {
				# if the value includes a'$' then eval	#
				if($data =~ /\$/) {
					$data=eval "return \"$data\";";
				}
				if(! -d $data || ! -w $data) {
					$error_str.="\nDefault specified log directory is not writable or does not exist.\n[Directory is '$data'].";
					next;
				}
				$ok=1;
			}
			#################################################
			# Handle physical link level checking variables	#
			#################################################
			if($_ eq "link_check_frequency") {
				if($data<=0 || $data>20) {
					$error_str.="\nValue of '$data' for '$_' is not valid - must be >0 and <=20.";
					next;
				}
				$clmasterdefaults::defaults{link_check_frequency}=$data;
				next;
			}
			if($_ eq "topology_check") {
				if($data<0 || $data>1) {
					$error_str.="\nValue of '$data' for '$_' is not valid - must be >=0 and <=1.";
					next;
				}
				$clmasterdefaults::defaults{topology_check}=$data;
				next;
			}
			if($_ eq "ip_swap_settle_time") {
				if($data<0 || $data>60) {
					$error_str.="\nValue of '$data' for '$_' is not valid - must be >=0 and <=60.";
					next;
				}
				$clmasterdefaults::defaults{ip_swap_settle_time}=$data;
				next;
			}
			if($_ eq "link_check_reset_time") {
				if($data<1 || $data>3600) {
					$error_str.="\nValue of '$data' for '$_' is not valid - must be >=1 and <=3600.";
					next;
				}
				$clmasterdefaults::defaults{link_check_reset_time}=$data;
				next;
			}
			if($_ eq "link_maxfails") {
				if($data<1 || $data>60) {
					$error_str.="\nValue of '$data' for '$_' is not valid - must be >=1 and <=60.";
					next;
				}
				$clmasterdefaults::defaults{link_maxfails}=$data;
				next;
			}
			if($_ eq "configdb_port" || $_ eq "statdb_port") {
				if($data<1024 || $data>99999) {
					$error_str.="\nValue of '$data' for '$_' is not valid - must be >=1024 and <=99999.";
					next;
				}
				$clmasterdefaults::defaults{$_}=$data;
				next;
			}

			#################################################
			# Check the port details...			#
			#################################################
			if($_ eq "portpool" || $_ eq "ports") {
				if($data !~ /^\d+,\d+$/) {
					$error_str.="\nValue specified for is not in NN,NN format.";
					next;
				}
				my @F=split(/,/,$data);
				if($F[0]<1024 || $F[0]>99999) {
					$error_str="\nPort value '$F[0]' is out of range for start of Port Pool.";
					next;
				}
				if($F[1]<1024 || $F[1]>99999) {
					$error_str="\nPort value '$F[1]' is out of range for end of Port Pool.";
					next;
				}
				if($F[1]<$F[0] || $F[1]-$F[0]<500) {
					$error_str="\nPort pool is too small!";
					next;
				}
				$clmasterdefaults::defaults{portpool}=$data;
				next;
			}
			#################################################
			# Handle maxblockdevs / total block devs.	#
			#################################################
			if($_ eq "maxblockdevs" || $_ eq "maxdrbddevs" || $_ eq "totalblockdevs") {
				if($data !~ /^\d+$/) {
					$error_str.="\nValue specified for Number of block devices is not a number.";
					next;
				}
				if($data<10 || $data>256) {
					$error_str="\nSupport range of block devices range from 10 to 256.";
					next;
				}
				$clmasterdefaults::defaults{maxblockdevs}=$data;
				next;
			}
			#################################################
			# Handle echotype / ping type			#
			#################################################
			if($_ eq "echotype" || $_ eq "pingtype") {
				if($data !~ /^(icmp|tcp|udp)$/i) {
					$error_str.="\nValue '$data' is invaid for echotype.";
					next;
				}
				$clmasterdefaults::defaults{echotype}=uc($data);
				next;
			}
			if($ok) {
				$clmasterdefaults::defaults{$_}=$data;
				next;
			}
			my $var=$_;
			if(grep {$_ eq $var} qw(starttimeout stoptimeout warntime formtime failtime bcast_threshold)) {
				if($data !~ /^\d+$/) {
					$error_str.="\nValue for '$_' must be integer.";
					next;
				}
				if(int($data)<5) {
					$error_str.="\nSpecified value '$data' for '$_' is too small.";
					next;
				}
				$clmasterdefaults::defaults{$_}=int($data);
				next;
			}
			#########################################################
			# Do the options that can have a minimum value of 1.	#
			#########################################################
			if(grep {$_ eq $var} qw(def_drbd1_sync def_drbd8_sync max_drbd1_devs max_drbd8_devs min_net_known_connections interval diskhb_interval diskhb_threshold)) {
				if($data !~ /^\d+$/) {
					$error_str.="\nValue for '$_' must be integer.";
					next;
				}
				if(int($data)<1) {
					$error_str.="\nSpecified value '$data' for '$_' is too small.";
					next;
				}
				$clmasterdefaults::defaults{$_}=int($data);
				next;
			}
			$error_str.="\nUnrecognised global option '$_'.";
		}
		return;
	}
	$error_str="Unrecognised element '$element' in defaults.xml!";
	$parser->xpcroak("Unrecognised element '$element' in defaults.xml!");
}

sub lc_handle_end {
my $parser=shift;

	$::__chardata =~ s/\s+//g;
	$::__chardata =~ s/\n+//g;
	if(length($::__chardata)) {
		$parser->xpcroak("Defaults XML file not expected to contain CHAR data.");
	}
	return;
}

sub lc_handle_char {
my $parser=shift;
my $str=shift;

	$::__chardata.=$str;
	return ;
}

sub get_defaults_list {
my $self=shift;
	return (sort(keys(%{$self->{DEFAULTS}})));
}

sub get_default_value {
my $self=shift;
my $attr=shift;
my $nowarn=shift;

	$nowarn=0 if !defined($nowarn);
	if(!exists($self->{DEFAULTS}->{$attr})) {
		croak("Default value '$attr' does not exist in config file.") if ! $nowarn; 
		return undef;
	}
	return $self->{DEFAULTS}->{$attr};
}

sub set_default_value {
my $self=shift;
my $attribute=shift;
my $value=shift;

	$self->{DEFAULTS}->{$attribute}=$value;
}

sub write_file {
my $self=shift;

	my $f=$self->{FILE};

	my $fd;
	open($fd,">$f.$$") or return(-1,"No write to file: $!");
	for my $l ("<?xml version=\"1.0\"?>","<defaults>","\t<global") {
		if(! print $fd "$l\n") {
			close($fd);
			unlink($f.$$);
			return(0,"File write failed: $!");
		}
	}
	for my $ca (sort keys (%{$self->{DEFAULTS}})) {
		my $s="\t\t$ca=\"" . $self->{DEFAULTS}->{$ca} . "\"\n";
		if(! print $fd $s) {
			close($fd);
			unlink($f.$$);
			return(0,"File write failed: $!");
		}
	}
	for my $l ("\t/>","</defaults>") {
		if(! print $fd "$l\n") {
			close($fd);
			unlink($f.$$);
			return(0,"File write failed: $!");
		}
	}
	close($fd);
	if(!rename("$f.$$",$f)) {
		unlink($f.$$);
		return(0,"File rename failed: $!");
	}
	return(1,undef);
}
1;
